import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

class CustomWrap extends MultiChildRenderObjectWidget {
  final double spacing; // 水平间距
  final double runSpacing; // 垂直间距

  CustomWrap({
    Key? key,
    required List<Widget> children,
    this.spacing = 0,
    this.runSpacing = 0,
  }) : super(key: key, children: children);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return _CustomWrapRenderBox(spacing: spacing, runSpacing: runSpacing);
  }

  @override
  void updateRenderObject(
      BuildContext context, covariant _CustomWrapRenderBox renderObject) {
    renderObject
      ..spacing = spacing
      ..runSpacing = runSpacing;
  }
}

class _CustomWrapParentData extends ContainerBoxParentData<RenderBox> {}

class _CustomWrapRenderBox extends RenderBox
    with
        ContainerRenderObjectMixin<RenderBox, _CustomWrapParentData>,
        RenderBoxContainerDefaultsMixin<RenderBox, _CustomWrapParentData> {
  double spacing;
  double runSpacing;

  _CustomWrapRenderBox({required this.spacing, required this.runSpacing});

  @override
  void setupParentData(RenderBox child) {
    if (child.parentData is! _CustomWrapParentData) {
      child.parentData = _CustomWrapParentData();
    }
  }

  @override
  void performLayout() {
    double maxWidth = constraints.maxWidth;
    double x = 0;
    double y = 0;
    double lineHeight = 0;

    RenderBox? child = firstChild;
    while (child != null) {
      child.layout(constraints.loosen(), parentUsesSize: true);
      final childSize = child.size;

      if (x + childSize.width > maxWidth) {
        // 换行
        x = 0;
        y += lineHeight + runSpacing;
        lineHeight = 0;
      }

      final childParentData = child.parentData as _CustomWrapParentData;
      childParentData.offset = Offset(x, y);

      x += childSize.width + spacing;
      lineHeight = lineHeight < childSize.height ? childSize.height : lineHeight;

      child = childParentData.nextSibling;
    }

    size = Size(maxWidth, y + lineHeight);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    RenderBox? child = firstChild;
    while (child != null) {
      final childParentData = child.parentData as _CustomWrapParentData;
      context.paintChild(child, childParentData.offset + offset);
      child = childParentData.nextSibling;
    }
  }

  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    RenderBox? child = lastChild;
    while (child != null) {
      final childParentData = child.parentData as _CustomWrapParentData;
      final bool isHit = result.addWithPaintOffset(
        offset: childParentData.offset,
        position: position,
        hitTest: (BoxHitTestResult result, Offset transformed) {
          return child!.hitTest(result, position: transformed);
        },
      );
      if (isHit) return true;
      child = childParentData.previousSibling;
    }
    return false;
  }
}
